/*****************************************************************************
 *   Copyright (C) 2008 John Schember <john@nachtimwald.com>                 *
 *                                                                           *
 *   This file is part of niwbillmanager.                                    *
 *                                                                           *
 *   niwbillmanager is free software: you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License as          *
 *   published by the Free Software Foundation, either version 3 of the      *
 *   License, or (at your option) any later version.                         *
 *                                                                           *
 *   niwbillmanager is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the            *
 *   GNU General Public License for more details.                            *
 *                                                                           *
 *   You should have received a copy of the GNU General Public License       *
 *   along with niwbillmanager. If not, see                                  *
 *   <http://www.gnu.org/licenses/>.                                         *
 *****************************************************************************/

#include <QFile>
#include <QFileInfo>
#include <QHashIterator>

#include <niwbillmanager.h>

#include "backendmanager.h"
#include "datastore.h"

Q_IMPORT_PLUGIN(backend_plaintext)

NiwBillManager::NiwBillManager()
{
    m_backendManager = new BackendManager();
    m_dataStore = new DataStore();

    m_lockMutex = new QMutex(QMutex::Recursive);

    m_errorMessage = "";
}

NiwBillManager::~NiwBillManager()
{
    delete m_dataStore;
    delete m_backendManager;
    delete m_lockMutex;
}

void NiwBillManager::lock()
{
    m_lockMutex->lock();
}

void NiwBillManager::unlock()
{
    m_lockMutex->unlock();
}

bool NiwBillManager::isBackendLoaded()
{
    QMutexLocker locker(m_lockMutex);

    if (m_backendManager->getCurrentBackend()) {
        return true;
    }

    m_errorMessage = "No backend loaded.";
    return false;
}

void NiwBillManager::newCollection()
{
    QMutexLocker locker(m_lockMutex);

    m_dataStore->clearCollection();
}

bool NiwBillManager::loadCollection(const QString &location,
    const QString &username, const QString &loginname, const QString &password)
{
    QMutexLocker locker(m_lockMutex);

    newCollection();

    if (m_backendManager->getCurrentBackend()->getBackendInterface()
        ->openCollection(BackendOpenMode::READ, location, username,
        loginname, password))
    {
        if (m_backendManager->getCurrentBackend()->getBackendInterface()
            ->canReadIncremental())
        {
            m_backendManager->getCurrentBackend()->getBackendInterface()
                ->startReadNextBillItem();
            while (m_backendManager->getCurrentBackend()->getBackendInterface()
                ->hasMoreBillItems())
            {
                BillItem billItem;

                if (m_backendManager->getCurrentBackend()
                    ->getBackendInterface()->readNextBillItem(billItem))
                {
                    m_dataStore->addBillItem(billItem, true);
                }
            }
        }
        else {
            QSet<BillItem> billItems;

            m_backendManager->getCurrentBackend()->getBackendInterface()
                ->readAllBillItems(billItems);
            Q_FOREACH (BillItem billItem, billItems) {
                m_dataStore->addBillItem(billItem, true);
            }
        }

        m_backendManager->getCurrentBackend()->getBackendInterface()
            ->closeCollection();
        return true;
    }

    m_errorMessage = m_backendManager->getCurrentBackend()
        ->getBackendInterface()->getLastErrorMessage();
    return false;
}

bool NiwBillManager::saveCollection(const QString &location,
    const QString &username, const QString &loginname, const QString &password)
{
    QMutexLocker locker(m_lockMutex);

    if (m_backendManager->getCurrentBackend()->getBackendInterface()
        ->openCollection(BackendOpenMode::WRITE, location, username,
        loginname, password))
    {
        if (m_backendManager->getCurrentBackend()->getBackendInterface()
            ->canSaveTransactional())
        {
            QHash<QString, TransactionType::TransactionTypes>
                shortTransactions;

            shortTransactions = createShortTransactionLog();

            // Get the billitem associated with the id, save the change
            // to disk via the backend.
            QHashIterator<QString, TransactionType::TransactionTypes>
                shortTransactionIterator(shortTransactions);
            while (shortTransactionIterator.hasNext()) {
                shortTransactionIterator.next();

                Transaction transaction;
                BillItem billItem;

                transaction.setTransactionType(
                    shortTransactionIterator.value());
                m_dataStore->getBillItemById(shortTransactionIterator.key(),
                    billItem);
                transaction.setBillItem(billItem);

                m_backendManager->getCurrentBackend()->getBackendInterface()
                    ->saveChange(transaction);
            }
        }
        else if (m_backendManager->getCurrentBackend()->getBackendInterface()
            ->canSaveAllIncremental())
        {
            m_dataStore->startGetAllBillItems();
            while (m_dataStore->hasMoreBillItems()) {
                BillItem billItem;
                m_dataStore->getNextBillItem(billItem);
                m_backendManager->getCurrentBackend()->getBackendInterface()
                    ->saveBillItem(billItem);
            }
        }
        else {
            QSet<BillItem> billItems;

            m_dataStore->startGetAllBillItems();
            while (m_dataStore->hasMoreBillItems()) {
                BillItem billItem;
                m_dataStore->getNextBillItem(billItem);
                billItems.insert(billItem);
            }
            m_backendManager->getCurrentBackend()->getBackendInterface()
                ->saveAllBillItems(billItems);
        }

        m_backendManager->getCurrentBackend()->getBackendInterface()
            ->closeCollection();
        m_dataStore->clearRawTransactionLog();
        return true;
    }

    m_errorMessage = m_backendManager->getCurrentBackend()
        ->getBackendInterface()->getLastErrorMessage();
    return false;
}

bool NiwBillManager::saveAsCollection(const QString &location,
    const QString &username, const QString &loginname, const QString &password)
{
    QMutexLocker locker(m_lockMutex);

    // Remove any data in the file so we can save into it.
    if (QFileInfo(location).exists()) {
        QFile(location).resize(0);
    }

    if (m_backendManager->getCurrentBackend()->getBackendInterface()
        ->openCollection(BackendOpenMode::WRITE, location, username,
        loginname, password))
    {
        if (m_backendManager->getCurrentBackend()->getBackendInterface()
            ->canSaveTransactional())
        {
            m_dataStore->startGetAllBillItems();
            while (m_dataStore->hasMoreBillItems()) {
                Transaction transaction;
                BillItem billItem;

                m_dataStore->getNextBillItem(billItem);

                transaction.setTransactionType(TransactionType::INSERT);
                transaction.setBillItem(billItem);

                m_backendManager->getCurrentBackend()->getBackendInterface()
                    ->saveChange(transaction);
            }
        }
        else if (m_backendManager->getCurrentBackend()->getBackendInterface()
            ->canSaveAllIncremental())
        {
            m_dataStore->startGetAllBillItems();
            while (m_dataStore->hasMoreBillItems()) {
                BillItem billItem;
                m_dataStore->getNextBillItem(billItem);
                m_backendManager->getCurrentBackend()->getBackendInterface()
                    ->saveBillItem(billItem);
            }
        }
        else {
            QSet<BillItem> billItems;

            m_dataStore->startGetAllBillItems();
            while (m_dataStore->hasMoreBillItems()) {
                BillItem billItem;
                m_dataStore->getNextBillItem(billItem);
                billItems.insert(billItem);
            }
            m_backendManager->getCurrentBackend()->getBackendInterface()
                ->saveAllBillItems(billItems);
        }

        m_backendManager->getCurrentBackend()->getBackendInterface()
            ->closeCollection();
        m_dataStore->clearRawTransactionLog();
        return true;
    }

    m_errorMessage = m_backendManager->getCurrentBackend()
        ->getBackendInterface()->getLastErrorMessage();
    return false;
}

bool NiwBillManager::shouldSave()
{
    QMutexLocker locker(m_lockMutex);

    m_dataStore->startGetRawTransactionLog();
    if (m_dataStore->hasMoreRawTransactions()) {
        return true;
    }

    return false;
}

QHash<QString, Backend *> NiwBillManager::getAvaliableBackends() const
{
    QMutexLocker locker(m_lockMutex);

    return m_backendManager->getAvaliableBackends();
}

bool NiwBillManager::setBackend(QString backendName)
{
    QMutexLocker locker(m_lockMutex);

    return m_backendManager->setCurrentBackend(backendName);
}

QString NiwBillManager::getBackendName()
{
    QMutexLocker locker(m_lockMutex);

    return m_backendManager->getCurrentBackend()->getName();
}

QString NiwBillManager::getBackendDescription()
{
    QMutexLocker locker(m_lockMutex);

    return m_backendManager->getCurrentBackend()->getDescription();
}

QString NiwBillManager::getBackendDescription(QString backendName)
{
    QMutexLocker locker(m_lockMutex);

    return m_backendManager->getBackendDescription(backendName);
}

QString NiwBillManager::getBackendVersion()
{
    QMutexLocker locker(m_lockMutex);

    return m_backendManager->getCurrentBackend()->getVersion();
}

QString NiwBillManager::getBackendVersion(QString backendName)
{
    QMutexLocker locker(m_lockMutex);

    return m_backendManager->getBackendVersion(backendName);
}

QString NiwBillManager::getBackendFileExtenstion()
{
    QMutexLocker locker(m_lockMutex);

    return m_backendManager->getCurrentBackend()->getBackendInterface()
        ->getDefaultFileExtenstion();
}

bool NiwBillManager::isBackendRemote()
{
    QMutexLocker locker(m_lockMutex);

    return m_backendManager->getCurrentBackend()->isRemote();
}

bool NiwBillManager::isBackendRemote(QString backendName)
{
    QMutexLocker locker(m_lockMutex);

    return m_backendManager->isBackendRemote(backendName);
}

QSet<QString> NiwBillManager::getAllTags()
{
    QMutexLocker locker(m_lockMutex);

    return m_dataStore->getAllTags();
}

QSet<QString> NiwBillManager::getTags(QString billId)
{
    QMutexLocker locker(m_lockMutex);

    return m_dataStore->getTags(billId);
}

QSet<BillItem> NiwBillManager::getBillItemsFromTags(QSet<QString> tags)
{
    QMutexLocker locker(m_lockMutex);

    QSet<BillItem> billItems;

    m_dataStore->startGetBillItemsFromTags(tags);
    while (m_dataStore->hasMoreBillItems()) {
        BillItem billItem;
        m_dataStore->getNextBillItem(billItem);
        billItems.insert(billItem);
    }

    return billItems;
}

void NiwBillManager::startGetBillItemsFromTags(QSet<QString> tags)
{
    QMutexLocker locker(m_lockMutex);

    m_dataStore->startGetBillItemsFromTags(tags);
}

QSet<BillItem> NiwBillManager::getBillItemsWithoutTags()
{
    QMutexLocker locker(m_lockMutex);

    QSet<BillItem> billItems;

    m_dataStore->startGetBillItemsWithoutTags();
    while (m_dataStore->hasMoreBillItems()) {
        BillItem billItem;
        m_dataStore->getNextBillItem(billItem);
        billItems.insert(billItem);
    }

    return billItems;
}

void NiwBillManager::startGetBillItemsWithoutTags()
{
    QMutexLocker locker(m_lockMutex);

    m_dataStore->startGetBillItemsWithoutTags();
}

QString NiwBillManager::newBillItem(BillItem billItem)
{
    QMutexLocker locker(m_lockMutex);

    return m_dataStore->addBillItem(billItem);
}

bool NiwBillManager::modifyBillItem(BillItem billItem)
{
    QMutexLocker locker(m_lockMutex);

    return m_dataStore->modifyBillItem(billItem);
}

bool NiwBillManager::deleteBillItem(QString billId)
{
    QMutexLocker locker(m_lockMutex);

    return m_dataStore->deleteBillItem(billId);
}

bool NiwBillManager::getBillItemById(QString billId, BillItem &billItem)
{
    QMutexLocker locker(m_lockMutex);

    return m_dataStore->getBillItemById(billId, billItem);
}

QSet<BillItem> NiwBillManager::getAllBillItems()
{
    QMutexLocker locker(m_lockMutex);

    QSet<BillItem> billItems;

    m_dataStore->startGetAllBillItems();
    while (m_dataStore->hasMoreBillItems()) {
        BillItem billItem;
        m_dataStore->getNextBillItem(billItem);
        billItems.insert(billItem);
    }

    return billItems;
}

void NiwBillManager::startGetAllBillItems()
{
    QMutexLocker locker(m_lockMutex);

    m_dataStore->startGetAllBillItems();
}

void NiwBillManager::startSearchBillItemsAnd(QSet<QString> query,
    bool amountDue, bool autoPay, bool nonAutoPay, bool dateDue, bool duePast,
    bool dueToday, bool dueFuture, bool payee, bool paymentLocation,
    bool paymentMethod, bool paymentRecurringPeriod, bool recurringOnly,
    bool nonRecurring, bool paid, bool unpaid, bool name, bool notes,
    bool tags)
{
    QMutexLocker locker(m_lockMutex);

    m_dataStore->startGetBillItems(query, QueryType::AND, amountDue,
        autoPay, nonAutoPay, dateDue, duePast, dueToday, dueFuture, payee,
        paymentLocation, paymentMethod, paymentRecurringPeriod, recurringOnly,
        nonRecurring, paid, unpaid, name, notes, tags);
}

void NiwBillManager::startSearchBillItemsOr(QSet<QString> query,
    bool amountDue, bool autoPay, bool nonAutoPay, bool dateDue, bool duePast,
    bool dueToday, bool dueFuture, bool payee, bool paymentLocation,
    bool paymentMethod, bool paymentRecurringPeriod, bool recurringOnly,
    bool nonRecurring, bool paid, bool unpaid, bool name, bool notes,
    bool tags)
{
    QMutexLocker locker(m_lockMutex);

    m_dataStore->startGetBillItems(query, QueryType::OR, amountDue,
        autoPay, nonAutoPay, dateDue, duePast, dueToday, dueFuture, payee,
        paymentLocation, paymentMethod, paymentRecurringPeriod, recurringOnly,
        nonRecurring, paid, unpaid, name, notes, tags);
}

bool NiwBillManager::hasMoreBillItems()
{
    QMutexLocker locker(m_lockMutex);

    return m_dataStore->hasMoreBillItems();
}

bool NiwBillManager::getNextBillItem(BillItem &billItem)
{
    QMutexLocker locker(m_lockMutex);

    return m_dataStore->getNextBillItem(billItem);
}

bool NiwBillManager::canUndoChange()
{
    QMutexLocker locker(m_lockMutex);

    return m_dataStore->canUndoChange();
}

bool NiwBillManager::canRedoChange()
{
    QMutexLocker locker(m_lockMutex);

    return m_dataStore->canRedoChange();
}

bool NiwBillManager::undoChange()
{
    QMutexLocker locker(m_lockMutex);

    Transaction transaction;

    return m_dataStore->undoChange(transaction);
}

bool NiwBillManager::undoChange(Transaction &transaction)
{
    QMutexLocker locker(m_lockMutex);

    return m_dataStore->undoChange(transaction);
}

bool NiwBillManager::redoChange()
{
    QMutexLocker locker(m_lockMutex);

    Transaction transaction;

    return m_dataStore->redoChange(transaction);
}

bool NiwBillManager::redoChange(Transaction &transaction)
{
    QMutexLocker locker(m_lockMutex);

    return m_dataStore->redoChange(transaction);
}

QString NiwBillManager::getLastErrorMessage()
{
    QMutexLocker locker(m_lockMutex);

    return m_errorMessage;
}

QHash<QString, TransactionType::TransactionTypes>
    NiwBillManager::createShortTransactionLog()
{
    QMutexLocker locker(m_lockMutex);

    QHash<QString, TransactionType::TransactionTypes> shortTransactions;

    m_dataStore->startGetRawTransactionLog();

    // Shorten the transaction list down to the changes that need to
    // be written to disk.
    while (m_dataStore->hasMoreRawTransactions()) {
        ShortTransaction shortTransaction;
        shortTransaction = m_dataStore->getNextRawTransaction();

        // Check if there is already a transaction for the bill.
        if (shortTransactions.contains(shortTransaction.getBillId())) {
            // If the transaction in the list is an INSERT, and the
            // TransactionType of the next item is a DELETE, then
            // then remove the bill from the list.
            if (shortTransactions.value(shortTransaction.getBillId())
                == TransactionType::INSERT
                && shortTransaction.getTransactionType()
                == TransactionType::DELETE)
            {
                shortTransactions.remove(shortTransaction.getBillId());
            }
            // If the transaction in the list is a DELETE, and the
            // TransactionType of the next item is an INSERT, then
            // change the type to and UPDATE. UPDATE to be save that no
            // data is lost. This situation can arrise from an undo.
            else if(shortTransactions.value(shortTransaction.getBillId())
                == TransactionType::DELETE
                && shortTransaction.getTransactionType()
                == TransactionType::INSERT)
            {
                shortTransactions.insert(shortTransaction.getBillId(),
                    TransactionType::UPDATE);
            }
        }
        // If there is no id in the list add the id and the
        // TransactionType.
        else {
            shortTransactions.insert(shortTransaction.getBillId(),
                shortTransaction.getTransactionType());
        }
    }

    return shortTransactions;
}
